#ifndef __M_MATCHER_H__
#define __M_MATCHER_H__

#include "util.hpp"
#include "db.hpp"
#include "online.hpp"
#include "room.hpp"
#include <list>
#include <mutex>
#include <condition_variable>


// 匹配队列的说明文档：
// 设计一个阻塞队列:(目的是用于实现玩家匹配队列)
// 功能:
//     1.入队数据
//     2.出队数据
//     3.移除指定的数据
//     4.线程安全
//     5.获取队列元素个数
//     6.阻塞
//     7.判断队列是否为空
//
// template<class K>
// class match_queue{
// private:
//     std::list<K> _list;              // 用链表而不直接使用queue是因为我们有中间删除数据的需要
//     std::mutex _mutex;               // 实现线程安全
//     std::condition_cariable _cond;   // 这个条件变量主要为了阻塞消费者，后边使用的时候：队列中元素个数 < 2 则阻塞
// public:
//     int size(){}                     // 获取元素个数
//     bool empty(){}                   // 判断是否为空
//     void wait(){}                    // 阻塞线程
//     void push(const K& data){}       // 入队数据，并唤醒线程
//     void pop(K& data){}              // 出队数据
//     void remove(K& data){}           // 移除指定的数据
// };


template<class K>
class match_queue{
private:
    std::list<K> _list;              // 用链表而不直接使用queue是因为我们有中间删除数据的需要
    std::mutex _mutex;               // 实现线程安全
    std::condition_variable _cond;   // 这个条件变量主要为了阻塞消费者，后边使用的时候：队列中元素个数 < 2 则阻塞
public:
    // 获取元素个数
    int size(){
        std::unique_lock<std::mutex> lock(_mutex);
        return _list.size();  
    }   

    // 判断是否为空
    bool empty(){
        std::unique_lock<std::mutex> lock(_mutex);
        return _list.empty();
    }        

    // 阻塞线程
    void wait(){
        std::unique_lock<std::mutex> lock(_mutex);
        _cond.wait(lock);   // condition_variable 是得配合 mutex 使用的哦
    }                    
    
    // 入队数据，并唤醒线程
    void push(const K& data){
        std::unique_lock<std::mutex> lock(_mutex);
        _list.push_back(data);
        _cond.notify_all();
    }       
    
    // 出队数据
    bool pop(K& data){
        std::unique_lock<std::mutex> lock(_mutex);
        if(_list.empty() == true) return false;
        data = _list.front();
        _list.pop_front();       // 出队列之后就得 pop --> 不然就会死循环式的创建房间
        return true;
    }              
    
    // 移除指定的数据
    void remove(K& data){
        std::unique_lock<std::mutex> lock(_mutex);
        _list.remove(data);
    }           
};




// 匹配管理文档说明:
//     1.三个不同档次的队列
//     2.三个线程分别对三个队列中的玩家进行匹配
//     3.房间管理模块的句柄
//     4.在线用户管理模块的句柄
//     5.数据管理模块-用户表的句柄
// 功能:
//     1.添加用户到匹配队列
//     2.从匹配队列移除用户
//     3.线程入口函数
//     判断指定队列是否人数大于2
//     出队两个玩家
//     创建房间，将两个玩家添加到房间中
//     向两个玩家发送对战匹配成功的消息


class matcher{
private:
    // 三个不同段位的匹配队列
    match_queue<uint64_t> _q_normal;
    match_queue<uint64_t> _q_high;
    match_queue<uint64_t> _q_super;
    // 对应三个匹配队列的处理线程
    std::thread _th_normal;
    std::thread _th_high;
    std::thread _th_super;

    room_manager* _rm;
    user_table* _ut;
    online_manager* _om;
private:
    // 传入三个对应的线程的函数执行的逻辑是一样的，只是操作的队列不同而已
    void handle_match(match_queue<uint64_t>& mq){
        while(1){
            // 1. 判断队列人数是否大于2，<2则阻塞等待
            while(mq.size() < 2){
                // 只有所在队列中有 mq.size() >= 2 时才跳出队列进行下面的匹配的过程的哦
                mq.wait();     
            }
            // 2. 走下来代表人数够了，出队两个玩家
            uint64_t uid1, uid2;
            bool ret = mq.pop(uid1);
            if(ret == false) continue;
            ret = mq.pop(uid2);
            if(ret == false){
                // 注意运行到这里说明，前面的uid1是pop出来的了。
                // 而现在uid2有问题，所以匹配不成功 --> 取出来的uid1就得重新加入队列中的哦
                this->add(uid1);  
                continue;
            }
            // 3. 校验两个玩家是否在线，如果有人掉线，则要吧另一个人重新添加入队列
            wsserver_t::connection_ptr conn1 = _om->get_conn_from_hall(uid1);
            if(conn1.get() == nullptr){
                // conn1.get() == nullptr 说明uid2虽然匹配取出来成功了，但是后面又退出什么问题 --> 得重新匹配
                // --> 取出来的uid2的再放入队列中去
                // 因为uid1都不在线了，所以uid1不用再加入队列了                
                this->add(uid2);
                continue;
            }
            wsserver_t::connection_ptr conn2 = _om->get_conn_from_hall(uid2);
            if(conn2.get() == nullptr){
                // conn2.get() == nullptr 说明uid1虽然匹配取出来成功了，但是后面又退出什么问题 --> 得重新匹配
                // --> 取出来的uid2的再放入队列中去
                // 因为uid2都不在线了，所以uid2不用再加入队列了                
                this->add(uid1);
                continue;
            }
            // 4. 为两个玩家创建房间，并将玩家加入房间中
            room_ptr rp = _rm->create_room(uid1, uid2);
            if(rp.get() == nullptr){
                // 房间创建失败，那么uid1和uid2得重新加入队列中去，重新匹配去
                this->add(uid1);
                this->add(uid2);
                continue;
            }
            // 5. 对两个玩家进行响应
            Json::Value resp;                  // 组织对应的数据返回
            resp["optype"] = "match_success";
            resp["result"] = true;
            std::string body;
            json_util::serialize(resp, body);  // 序列化一下
            conn1->send(body);
            conn2->send(body);
        }
    }
    void th_normal_entry(){ return handle_match(_q_normal); }
    void th_high_entry(){ return handle_match(_q_high); }
    void th_super_entry(){ return handle_match(_q_super); }
public:
    // 记得初始化线程的哦，上面只是对三个线程的声明的
    matcher(room_manager* rm, user_table* ut, online_manager* om):
        _rm(rm), _ut(ut), _om(om),
        _th_normal(std::thread(&matcher::th_normal_entry, this)),
        _th_high(std::thread(&matcher::th_high_entry, this)),
        _th_super(std::thread(&matcher::th_super_entry, this)){
        DLOG("游戏匹配模块初始化完毕......");
    }
    // ~matcher(){}

    // 根据用户的分数，添加用户到对应的队列
    bool add(uint64_t uid){
        // 根据玩家的天梯分数，来判定玩家档次，添加到不同的匹配队列
        // 1. 根据用户ID，获取玩家信息
        Json::Value user;
        bool ret = _ut->select_by_id(uid, user);
        if(ret == false){
            DLOG("获取玩家：%d 信息失败！！！", uid);
            return false;
        }
        int score = user["score"].asInt();
        // 2. 添加uid到指定的队列中
        if(score < 2000){
            _q_normal.push(uid);
        }else if(score >= 2000 && score < 3000){
            _q_high.push(uid);
        }else{
            _q_super.push(uid);
        }
        return true;
    }

    // 根据用户id，找到所在不同的队列删除用户
    bool del(uint64_t uid){
        // 根据玩家的天梯分数，来判定玩家档次，从对应的匹配队列中删除
        // 1. 根据用户ID，获取玩家信息
        Json::Value user;
        bool ret = _ut->select_by_id(uid, user);
        if(ret == false){
            DLOG("获取玩家：%d 信息失败！！！", uid);
            return false;
        }
        int score = user["score"].asInt();
        // 2. 在对应的队列中输出uid
        if(score < 2000){
            _q_normal.remove(uid);
        }else if(score >= 2000 && score < 3000){
            _q_high.remove(uid);
        }else{
            _q_super.remove(uid);
        }
        return true;
    }
};


#endif

